package mosdi.subcommands;

import java.util.ArrayList;
import java.util.List;

import mosdi.fa.AutomataUtils;
import mosdi.index.SuffixTree;
import mosdi.index.SuffixTreeWalker;
import mosdi.matching.Matcher;
import mosdi.paa.MinimizedDAA;
import mosdi.paa.apps.PatternMatchingDAA;
import mosdi.tools.PatternMatchingAnalysis;
import mosdi.util.Alphabet;
import mosdi.util.BitArray;
import mosdi.util.Log;

public class ShowDAASubcommand extends Subcommand {

	@Override
	public String description() {
		return "Prints a DAA description for given pattern and algorithm.";
	}

	@Override
	public String usage() {
		return super.usage()+" [options] <algorithm> <pattern>\n" +
		"\n" +
		"Prints a DAA description for given pattern and algorithm.\n" +
		"\n" +
		"Possible algorithms: "+ PatternMatchingAnalysis.allMatcherNames() +"\n" +
		"\n" +
		"Options:\n" +
		"  -A <alphabet>: default: ACGT";
	}

	@Override
	public String name() {
		return "print-daa";
	}

	@Override
	public int run(String[] args) {
		parseOptions(args, 2, "A:");

		// Option dependencies
		// -- none --

		// Mandatory arguments
		String algorithmName = getStringArgument(0);
		String patternString = getStringArgument(1);

		// Options
		Alphabet alphabet = Alphabet.getDnaAlphabet();
		if (optionSet.has("A")) alphabet = new Alphabet((String)optionSet.valueOf("A"));
		
		int[] pattern = alphabet.buildIndexArray(patternString);
		Matcher matcher = PatternMatchingAnalysis.getMatcher(algorithmName, alphabet.size(), pattern);
		if (matcher==null) {
			Log.errorln("Error: unknown algorithm: "+algorithmName+".");
			return 1;
		}
		PatternMatchingDAA daa = new PatternMatchingDAA(matcher.costScheme(), 0);
		MinimizedDAA minimizedDaa = new MinimizedDAA(daa);
		Log.printf(Log.Level.STANDARD, "  Minimized DAA for %s:\n", alphabet.buildString(pattern));
		BitArray reachability = AutomataUtils.findReachableStates(minimizedDaa);
		for (int state=0; state<minimizedDaa.getStateCount(); ++state) {
			int emission = minimizedDaa.getEmission(state);
			Log.printf(Log.Level.STANDARD, " %c%c State %d (cost: %d, transitions:", reachability.get(state)?' ':'U', state==minimizedDaa.getStartState()?'S':' ', state, emission);
			for (int c=0; c<alphabet.size(); ++c) {
				Log.printf(Log.Level.STANDARD, " %c:%d",alphabet.get(c),minimizedDaa.getTransitionTarget(state, c));
			}
			Log.printf(Log.Level.STANDARD, "):");
			StringBuffer allWindows = new StringBuffer();
			List<int[]> windowList = new ArrayList<int[]>(0);
			int pos = -1;
			for (int originalState : minimizedDaa.getEquivalencePartition().block(state)) {
				int[] window = daa.decodeStateToWindow(originalState);
				windowList.add(window);
				if (pos==-1) pos = daa.decodeStateToPosition(originalState);
				if (pos!=daa.decodeStateToPosition(originalState)) throw new IllegalStateException("Different positions in one equivalence class! BUG?!?");
				if (allWindows.length()>0) allWindows.append(',');
				allWindows.append(alphabet.buildString(window));
				// Log.printf(Log.Level.STANDARD, " %s/%d", alphabet.buildString(window), pos);
			}
			Log.printf(Log.Level.STANDARD, " pos: %d, windows: %s (%s)\n", pos, condenseWindowList(windowList,alphabet), allWindows.toString());
		}
		
		return 0;
	}

	private String condenseWindowList(List<int[]> windowList, Alphabet alphabet) {
		if (windowList.isEmpty()) return "";
		int windowSize = windowList.get(0).length;
		int[] joinedSequence = new int[(windowSize+1)*windowList.size()];
		int i = 0;
		for (int[] window : windowList) {
			if (window.length != windowSize) throw new IllegalArgumentException("Window size mismatch");
			for (int j=window.length-1; j>=0; --j) {
				joinedSequence[i++] = window[j];
			}
			joinedSequence[i++] = -1;
		}
		SuffixTree suffixTree = new SuffixTree(joinedSequence, alphabet.size());
		int[] countAnnotation = suffixTree.calcSubstringCountAnnotation(windowSize);
		SuffixTreeWalker walker = suffixTree.getWalker();
		StringBuffer results = new StringBuffer();
		condenseWindowListRecursive(walker, countAnnotation, "", results, (long)Math.pow(alphabet.size(),windowSize), alphabet);
		return results.toString();
	}

	private void condenseWindowListRecursive(SuffixTreeWalker walker, int[] countAnnotation, String currentString, StringBuffer results, long count, Alphabet alphabet) {
		int[] nodes = walker.getNodes();
		if (nodes.length!=1) throw new IllegalStateException();
		int node = nodes[0];
		if (countAnnotation[node]==count) {
			if (results.length()>0) results.append(',');
			results.append("<"+currentString+">");
			return;
		}
		long nextCount = count / alphabet.size();
		String completeChars = "";
		for (int c=0; c<alphabet.size(); ++c) {
			nodes = walker.forward(c);
			if (nodes.length==1) {
				int childNode = nodes[0];
				if (countAnnotation[childNode] == nextCount) {
					completeChars += alphabet.get(c);
				} else {
					condenseWindowListRecursive(walker, countAnnotation, ""+alphabet.get(c)+currentString, results, nextCount, alphabet);
				}
			}
			walker.backward(currentString.length());
		}
		if (completeChars.length()>0) {
			if (results.length()>0) results.append(',');
			if (completeChars.length()==1) {
				results.append("<"+completeChars+currentString+">");
			} else {
				results.append("<["+completeChars+"]"+currentString+">");
			}
		}
	}

}
